﻿////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright file="AbstractGameCollection.cs" company="SingularityShift.com">
// Copyright (c) 2010 SingularityShift.com. All rights reserved.
// </copyright>
// <author>Ben McClure</author>
// <email>ben@singularityshift.com</email>
// <date>11/24/2010</date>
// <summary>Implements the abstract game collection class</summary>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameLib.Games
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Text.RegularExpressions;
    using GameLib.Games.Components;
    using GameLib.Games.Util;

    ////////////////////////////////////////////////////////////////////////////////////////////////////
    /// <summary>   Collection of abstract games. </summary>
    ///
    /// <remarks>   Ben McClure, 11/24/2010. </remarks>
    ////////////////////////////////////////////////////////////////////////////////////////////////////
    public abstract class AbstractGameCollection<T> : BindingList<T>, IGameCollection<T> where T : IGame
    {
        #region IGameCollection<T> Event Handlers

        /// <summary> Event queue for all listeners interested in GameListImported events. </summary>
        public event EventHandler<EventArgs> GameListImported;

        /// <summary> Event queue for all listeners interested in GameListExported events. </summary>
        public event EventHandler<EventArgs> GameListExported;

        #endregion IGameCollection<T> Event Handlers

        #region IGameCollection<T> Members

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Gets or sets an array of games. </summary>
        ///
        /// <value> An Array of games. </value>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public T[] GameArray
        {
            get
            {
                return this.ToArray();
            }

            protected set
            {
                this.Clear();
                foreach (T game in value)
                {
                    this.Add(game);
                }
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Indexer to get or set items within this collection using array index syntax. </summary>
        ///
        /// <value> The indexed item. </value>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public T this[string id]
        {
            get
            {
                IList<T> found = this.Find("Id", id);
                if (found.Count > 0)
                {
                    return found[0];
                }
                else
                {
                    return default(T);
                }
            }

            set
            {
                IList<T> found = this.Find("Id", id);
                foreach (T game in found)
                {
                    this.Remove(game);
                }

                this.Add(value);
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Gets or sets the GameSortTypes enum that defines how this list should be sorted. </summary>
        ///
        /// <value> The amount to sort by. </value>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public GameSortTypes SortBy
        {
            get;
            set;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Gets or sets the DateTime object indicating when this list was created. </summary>
        ///
        /// <value> The created. </value>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public DateTime Created
        {
            get;
            set;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Initialises this object from the given from array. </summary>
        ///
        /// <param name="games">    The games. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public void FromArray(T[] games)
        {
            this.Clear();
            foreach (T game in games)
            {
                this.Add(game);
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Inserts a game after another one. </summary>
        ///
        /// <param name="thisGame">     this game. </param>
        /// <param name="otherGame">    The other game. </param>
        ///
        /// <returns>   . </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public int InsertAfter(T thisGame, T otherGame)
        {
            int index = this.IndexOf(otherGame);
            this.Insert(index + 1, thisGame);
            return index + 1;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Inserts a game before another one. </summary>
        ///
        /// <param name="thisGame">     this game. </param>
        /// <param name="otherGame">    The other game. </param>
        ///
        /// <returns>   . </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public int InsertBefore(T thisGame, T otherGame)
        {
            int index = this.IndexOf(otherGame);
            this.Insert(index, thisGame);
            return index;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Query if this collection contains thisGame. </summary>
        ///
        /// <param name="thisGame"> this game. </param>
        ///
        /// <returns>   true if it succeeds, false if it fails. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public bool ContainsGame(T game)
        {
            foreach (T thisGame in this)
            {
                if (game.Id.Equals(thisGame.Id, StringComparison.CurrentCultureIgnoreCase))
                {
                    return true;
                }
            }

            return false;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Removes the game described by thisGame. </summary>
        ///
        /// <param name="thisGame"> this game. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public void RemoveGame(T thisGame)
        {
            foreach (T game in this)
            {
                if (thisGame.Id.Equals(game.Id, StringComparison.CurrentCultureIgnoreCase))
                {
                    this.Remove(game);
                    return;
                }
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Convert this object into an array representation. </summary>
        ///
        /// <returns>   An array representation of this object. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public T[] ToArray()
        {
            return this.Items.ToArray();
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Searches for the first match. </summary>
        ///
        /// <param name="field">    The field. </param>
        /// <param name="value">    The value. </param>
        ///
        /// <returns>   A list of. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public IGameCollection<T> Find(string field, object value)
        {
            return Find(field, value, false);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Query if this collection contains thisGame. </summary>
        ///
        /// <param name="id">   The identifier. </param>
        ///
        /// <returns>   true if it succeeds, false if it fails. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public bool ContainsGame(string id)
        {
            foreach (T game in this)
            {
                if (game.Id.Equals(id, StringComparison.CurrentCultureIgnoreCase))
                {
                    return true;
                }
            }

            return false;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Exports. </summary>
        ///
        /// <param name="textFile"> The text file. </param>
        ///
        /// <returns>   number of items exported. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public int Export(string textFile)
        {
            return Export(textFile, false);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Imports games from a text file. </summary>
        ///
        /// <param name="textFile"> The text file. </param>
        ///
        /// <returns>   . </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public int Import(string textFile)
        {
            return Import(textFile, GameSortTypes.ActualOrder);
        }

        #endregion IGameCollection<T> Members

        #region IGameCollection<T> Virtual Members

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Searches for the first match. </summary>
        ///
        /// <param name="field">    The field. </param>
        /// <param name="value">    The value. </param>
        /// <param name="regex">    true to regex. </param>
        ///
        /// <returns>   A list of games. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public virtual IGameCollection<T> Find(string field, object value, bool regex)
        {
            IGameCollection<T> result = CreateGameCollection();

            Regex rx = null;
            if (regex)
            {
                rx = new Regex(@value.ToString());
            }

            foreach (T game in this)
            {
                Type type = game.GetType();
                PropertyInfo fieldInfo = type.GetProperty(field);
                if (fieldInfo == null)
                {
                    throw new MemberAccessException("Cannot locate field " + field);
                }

                if (regex)
                {
                    if (rx.IsMatch(fieldInfo.GetValue(game, null).ToString()))
                    {
                        result.Add(game);
                    }
                }
                else if (value.Equals(fieldInfo.GetValue(game, null)))
                {
                    result.Add(game);
                }
            }

            return result;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Query if 'pattern' contains pattern. </summary>
        ///
        /// <param name="pattern">  The pattern. </param>
        ///
        /// <returns>   true if it succeeds, false if it fails. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public virtual bool ContainsPattern(string pattern)
        {
            pattern = (pattern.Substring(0, 1).Equals("@")) ? pattern.Substring(1) : Regex.Escape(pattern);
            Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);
            foreach (T game in this)
            {
                if (regex.IsMatch(game.Id))
                {
                    return true;
                }
            }

            return false;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Exports. </summary>
        ///
        /// <param name="textFile">     The text file. </param>
        /// <param name="overwrite">    true to overwrite, false to preserve. </param>
        ///
        /// <returns>   number of items exported. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public virtual int Export(string textFile, bool overwrite)
        {
            if ((!overwrite) && File.Exists(textFile))
            {
                throw new IOException(textFile + " exists and was not overwritten");
            }

            int exported = 0;
            bool append = false;
            append = overwrite ? false : true;
            using (StreamWriter sw = new StreamWriter(textFile, append))
            {
                using (TextWriter output = TextWriter.Synchronized(sw))
                {
                    foreach (T game in this)
                    {
                        if (exported > 0)
                        {
                            output.WriteLine();
                        }

                        output.Write(game.Id);
                        exported++;
                    }
                }
            }

            this.OnGameListExported(new EventArgs());
            return exported;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Imports games from a text file. </summary>
        ///
        /// <param name="dir">              The dir. </param>
        /// <param name="searchPattern">    The search pattern. </param>
        /// <param name="sortBy">           Sort type to use. </param>
        ///
        /// <returns>   . </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public virtual int Import(string dir, string searchPattern, Util.GameSortTypes sortBy)
        {
            if ((!Directory.Exists(dir)) || dir == null)
            {
                throw new DirectoryNotFoundException("Import directory not found");
            }

            IList<T> added = new List<T>();
            string[] filePaths = Directory.GetFiles(dir, searchPattern, SearchOption.TopDirectoryOnly);
            int imported = 0;
            lock (this.Items)
            {
                foreach (string filePath in filePaths)
                {
                    T game = this.CreateGame(Path.GetFileName(filePath));
                    game.Exe = filePath;
                    game.Title = Path.GetFileNameWithoutExtension(filePath);
                    this.Add(game);
                    added.Add(game);
                    imported++;
                }

                this.SortGames(sortBy);
            }

            this.OnGameListImported(new EventArgs());
            return imported;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Imports games from a text file. </summary>
        ///
        /// <param name="textFile"> The text file. </param>
        /// <param name="sortBy">   Sort type to use. </param>
        ///
        /// <returns>   . </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public virtual int Import(string textFile, Util.GameSortTypes sortBy)
        {
            int imported = 0;
            if (!File.Exists(textFile))
            {
                throw new FileNotFoundException("Import file " + textFile + " not found", textFile);
            }

            string[] lines = File.ReadAllLines(textFile);
            List<T> added = new List<T>();
            lock (this.Items)
            {
                foreach (string line in lines)
                {
                    T game = CreateGame(line);
                    game.Title = Path.GetFileNameWithoutExtension(line);
                    game.Exe = line;
                    this.Add(game);
                    added.Add(game);
                    imported++;
                }

                this.SortGames(sortBy);
            }

            this.OnGameListImported(new EventArgs());
            return imported;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Sort games. </summary>
        ///
        /// <param name="sortBy">   Amount to sort by. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public void SortGames(Util.GameSortTypes sortBy)
        {
            lock (this.Items)
            {
                List<T> list = (List<T>)this.Items;
                switch (sortBy)
                {
                    case GameSortTypes.Title:
                        list.Sort(new GameComparer<T>(GameComparisonType.Title, false));
                        break;
                    case GameSortTypes.TitleReverse:
                        list.Sort(new GameComparer<T>(GameComparisonType.Title, true));
                        break;
                    case GameSortTypes.Id:
                        list.Sort(new GameComparer<T>(GameComparisonType.Id, false));
                        break;
                    case GameSortTypes.IdReverse:
                        list.Sort(new GameComparer<T>(GameComparisonType.Id, true));
                        break;
                    case GameSortTypes.Exe:
                        list.Sort(new GameComparer<T>(GameComparisonType.Exe, false));
                        break;
                    case GameSortTypes.ExeReverse:
                        list.Sort(new GameComparer<T>(GameComparisonType.Exe, true));
                        break;
                    case GameSortTypes.TimeAccessed:
                        list.Sort(new GameComparer<T>(GameComparisonType.TimeAccessed, false));
                        break;
                    case GameSortTypes.TimeAccessedReverse:
                        list.Sort(new GameComparer<T>(GameComparisonType.TimeAccessed, true));
                        break;
                }
            }

            this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Makes a deep copy of this object. </summary>
        ///
        /// <returns>   A copy of this object. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        public object Clone()
        {
            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            bf.Serialize(ms, this);
            ms.Flush();
            ms.Position = 0;
            return (IGameCollection<T>)bf.Deserialize(ms);
        }

        #endregion IGameCollection<T> Virtual Members

        #region Abstract Members

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Creates the game collection. </summary>
        ///
        /// <returns>   A list of. </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        protected abstract IGameCollection<T> CreateGameCollection();

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Creates a game. </summary>
        ///
        /// <param name="id">   The identifier. </param>
        ///
        /// <returns>   . </returns>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        protected abstract T CreateGame(string id);

        #endregion Abstract Members

        #region Members

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Raises the game list imported event. </summary>
        ///
        /// <remarks>   Ben McClure, 11/24/2010. </remarks>
        ///
        /// <param name="eventArgs">    Event information to send to registered event handlers. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        protected void OnGameListImported(EventArgs eventArgs)
        {
            if (this.GameListImported != null)
            {
                this.GameListImported(this, eventArgs);
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Raises the game list exported event. </summary>
        ///
        /// <remarks>   Ben McClure, 11/24/2010. </remarks>
        ///
        /// <param name="eventArgs">    Event information to send to registered event handlers. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        protected void OnGameListExported(EventArgs eventArgs)
        {
            if (this.GameListExported != null)
            {
                this.GameListExported(this, eventArgs);
            }
        }

        #endregion Members
    }
}